﻿#include "testwidget.h"
#include "ui_testwidget.h"
#include "model/datamanager.h"
#include "3rdparty/reportoutput/reportoutput.h"

#include <model/testplanbuilder.h>

#include <common/calcutils.h>
#include <common/common.h>

/**
 * @brief TestWidget::TestWidget
 * @details 创建测试界面并根据阶段初始化 UI 与线程。
 * @param stage 当前测试流程阶段。
 * @param parent Qt 父窗口。
 */
TestWidget::TestWidget(TestProcessStage stage, QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::TestWidget)
    , m_stage(stage)
    , m_startFreq(1.000)
    , m_stopFreq(1000.000)
{
    ui->setupUi(this);
    qRegisterMetaType<Exec::TestPlanPtr>("Exec::TestPlanPtr");
    qRegisterMetaType<MeasurementPoint>("MeasurementPoint");
    qRegisterMetaType<LevelScanningConfig>("LevelScanningConfig");
    qRegisterMetaType<LevelScanningData>("LevelScanningData");
    qRegisterMetaType<DeviceMeasureStatus>("DeviceMeasureStatus");

    m_testResultsBuffer.clear();

    initUiAndSignals();
    initPlot();
    initLevelScanningUI();
    ensureRunnerThread();

    QSignalBlocker blocker(ui->comboBox_template); // 临时阻塞信号
    // 填充模板列表
    const auto ovs = DataManager::instance()->getTestTemplateOverviews();
    for (const auto &ov : ovs) {
        ui->comboBox_template->addItem(ov.name, ov.id);
    }
}

/**
 * @brief TestWidget::~TestWidget
 * @details 停止测试线程并释放界面资源。
 */
TestWidget::~TestWidget()
{
    shutdownRunner();
    delete ui;
}

/**
 * @brief TestWidget::initPlot
 * @details 初始化测试曲线图表并布置布局。
 */
void TestWidget::initPlot()
{
    key_Result.clear();
    plotGeneratorOutput = new MyCustomplot();
    plotGeneratorOutput->initValue(0, 0, m_startFreq, m_stopFreq, 0, 10,
                                   tr("Frequency MHz"), tr("GeneratorOutput dBm")); //tr("频率 MHz"), tr("发生器输出 dBm"));
    plotGeneratorOutput->setLine(m_startFreq, 2, QColor(72, 72, 255) /*QColor(0, 103, 15)*/);

    plotImmunityLevel = new MyCustomplot();
    plotImmunityLevel->initValue(0, 0, m_startFreq, m_stopFreq, 0, 20,
                                 tr("Frequency MHz"), tr("Immunity Level") + " V/m");
    plotImmunityLevel->setLine(m_startFreq, 5, QColor(72, 72, 255));

    plotAmplifierFWD = new MyCustomplot();
    plotAmplifierFWD->initValue(0, 0, m_startFreq, m_stopFreq, -30, 3,
                                tr("Frequency MHz"), tr("AmplifierFWD dBm")); //tr("频率 MHz"), tr("功放前向功率 dBm"));
    plotAmplifierFWD->setLine(m_startFreq, -5, QColor(72, 72, 255));

    plotAmplifierREV = new MyCustomplot();
    plotAmplifierREV->initValue(0, 0, m_startFreq, m_stopFreq, 0, 10,
                                tr("Frequency MHz"), tr("AmplifierREV dBm")); //tr("频率 MHz"), tr("功放反向功率 dBm"));
    plotAmplifierREV->setLine(m_startFreq, 1, QColor(72, 72, 255));

    //    plotCycleTest = new MyCustomplot();
    //    plotCycleTest->initValue(0, 0, 0, 66, 0, 10,
    //                             tr("Time s"), tr("Immunity Level") + " V");
    //    plotCycleTest->setLine(0, 0, QColor(72, 72, 255));

    plotVSWR = new MyCustomplot();
    plotVSWR->initValue(0, 0, m_startFreq, m_stopFreq, 0, 3,
                        tr("Frequency MHz"), tr("VSWR")); //tr("频率 MHz"), tr("驻波比"));
    plotVSWR->setLine(m_startFreq, 0, QColor(72, 72, 255));
    plotVSWR->hide();
    //根据配置是否显示
    QGridLayout *gridLayout = new QGridLayout();
    int row = 0, column = 0;
    plotAmplifierFWD->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    gridLayout->addWidget(plotAmplifierFWD, row, column++);
    if (column > 1) {
        row += 1;
        column = 0;
    }
    plotAmplifierREV->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    gridLayout->addWidget(plotAmplifierREV, row, column++);
    if (column > 1) {
        row += 1;
        column = 0;
    }
    plotGeneratorOutput->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    gridLayout->addWidget(plotGeneratorOutput, row, column++);
    if (column > 1) {
        row += 1;
        column = 0;
    }

    plotImmunityLevel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    gridLayout->addWidget(plotImmunityLevel, row, column++);
    if (column > 1) {
        row += 1;
        column = 0;
    }

    plotVSWR->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    gridLayout->addWidget(plotVSWR, row, column++);
    if (column > 1) {
        row += 1;
        column = 0;
    }

    //    if (m_stage == TestProcessStage::Testing) {
    /************ 循环测试 ************/
    //        plotCycleTest->setSign(true);
    //        plotCycleTest->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    //        gridLayout->addWidget(plotCycleTest, row, column++);
    //        plotCycleTest->hide();
    //    }
    gridLayout->setMargin(0);
    gridLayout->setSpacing(0);

    QVBoxLayout *vLayout = new QVBoxLayout(ui->testFrame);
    vLayout->addLayout(gridLayout);
    ui->testFrame->setLayout(vLayout);
}

/**
 * @brief TestWidget::initLevelScanningUI
 * @details 针对电平扫描阶段调整界面控件与配置。
 */
void TestWidget::initLevelScanningUI()
{
    if (m_stage != TestProcessStage::LevelScanning)
        return;
    ui->stackedWidget->setCurrentIndex(1);
    plotVSWR->hide();
    plotAmplifierFWD->hide();
    plotAmplifierREV->hide();
    plotGeneratorOutput->hide();

    QStringList models = DataManager::instance()->getDeviceModelList(DeviceType::Receiver);
    QSignalBlocker blocker(ui->comboBox_Model);
    ui->comboBox_Model->addItems(models);
    ui->tBtn_fwdPlay->setEnabled(true);
    connect(DataManager::instance(), &DataManager::sig_parameterTablesChanged,
            this, &TestWidget::slot_parameterTablesChanged);
    slot_parameterTablesChanged();

    loadLevelScanConfig();
}

/**
 * @brief TestWidget::ensureRunnerThread
 * @details 确保测试执行线程及其对象已创建并连接信号。
 */
void TestWidget::ensureRunnerThread()
{
    if (runner_)
        return;
    runnerThread_ = new QThread(this);
    runner_ = new TestRunner();
    runner_->moveToThread(runnerThread_);

    // 线程结束时清理 runner
    connect(runnerThread_, &QThread::finished, runner_, &QObject::deleteLater);

    // Runner 信号
    connect(runner_, &TestRunner::stateChanged, this, &TestWidget::onStateChanged, Qt::QueuedConnection);
    if (m_stage == TestProcessStage::LevelScanning)
        connect(runner_, &TestRunner::sig_levelScanData, this, &TestWidget::onLevelScanningData, Qt::QueuedConnection);
    else
        connect(runner_, &TestRunner::pointProgress, this, &TestWidget::onPointMeasured, Qt::QueuedConnection);

    connect(runner_, &TestRunner::sig_updateDeviceStatus, this, &TestWidget::sig_updateDeviceStatus, Qt::QueuedConnection);
    connect(runner_, &TestRunner::sweepFinished, this, &TestWidget::onSweepFinished, Qt::QueuedConnection);
    connect(
        runner_, &TestRunner::logMessage, this, [this](const QString &s) {
            appendLog(LogLevel::Info, s);
        },
        Qt::QueuedConnection);
    connect(
        runner_, &TestRunner::errorOccurred, this, [this](const QString &s) {
            appendLog(LogLevel::Error, s);
        },
        Qt::QueuedConnection);

    connect(runner_, &TestRunner::manualChangeRequired, this, &TestWidget::onManualChangeRequired, Qt::QueuedConnection);
    runnerThread_->start();
}

/**
 * @brief TestWidget::shutdownRunner
 * @details 请求测试线程停止并清理相关资源。
 */
void TestWidget::shutdownRunner()
{
    if (!runnerThread_)
        return;

    // 请求停止
    if (runner_)
        runner_->stop();
    //        QMetaObject::invokeMethod(runner_, "stop", Qt::QueuedConnection);

    QMetaObject::invokeMethod(runner_, "shutdownAndRelease", Qt::QueuedConnection);

    // 3) 退出线程
    runnerThread_->quit();
    runnerThread_->wait(2000);
    runnerThread_ = nullptr;
    runner_ = nullptr;
}

/**
 * @brief TestWidget::buildPlanFromCurrentTemplate
 * @details 根据当前选择的测试模板构建执行计划并刷新图表。
 * @return 构建成功返回 true。
 */
bool TestWidget::buildPlanFromCurrentTemplate()
{
    plan_.reset();
    m_testResultsBuffer.clear();
    const int tplId = ui->comboBox_template->currentData().toInt();
    if (tplId <= 0) {
        //        qWarning() << "comboBox_template currentIndex <= 0 ";
        return false;
    }
    // 加载完整模板（可编辑层）
    TestTemplatePtr tpl = DataManager::instance()->getFullTestTemplate(tplId);
    if (!tpl) {
        QMessageBox::critical(this, "错误", "加载测试模板失败");
        return false;
    }
    tpl->stage = m_stage;
    bool isRS = (tpl->flowKind == HardwareTemplateType::RS) ? true : false;
    if (isRS)
        (ui->radioButton_H->isChecked()) ? tpl->antennaFactor = AntennaFactorType::Horizontal : tpl->antennaFactor = AntennaFactorType::Vertical;

    ui->radioButton_H->setVisible(isRS);
    ui->radioButton_V->setVisible(isRS);
    ui->label_NominalUnit->setText(tpl->immunityLevelUnit);
    ui->label_CurrentUnit->setText(tpl->immunityLevelUnit);

    // 构建快照（只读）
    TestPlanBuilder builder;
    Exec::TestPlanPtr p = builder.build(*tpl);
    if (!p) {
        QMessageBox::critical(this, "错误", "构建测试计划失败,请查看日志。");
        return false;
    }

    plan_ = p;
    QMetaObject::invokeMethod(runner_, "setPlan", Qt::QueuedConnection, Q_ARG(Exec::TestPlanPtr, plan_));

    // 游标初始化（默认第一个子区间、第一个频点）
    currentSubrangeIndex_ = 0;
    currentFrequencyIndex_ = 0;
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));

    // 获取全局频率范围
    double minHz = 0, maxHz = 0;
    if (!getGlobalFreqRangeHz(minHz, maxHz)) {
        // 错误处理或使用默认值
        minHz = 1e6;
        maxHz = 1000e6;
    }
    const double xMinMHz = minHz / 1e6;
    const double xMaxMHz = maxHz / 1e6;

    // UI 同步
    ui->lineEdit_startF->setText(QString::number(xMinMHz, 'f', 6));
    ui->lineEdit_stopF->setText(QString::number(xMaxMHz, 'f', 6));
    m_startFreq = xMinMHz;
    m_stopFreq = xMaxMHz;
    ui->lineEdit_Freq->setText(QString::number(m_startFreq, 'f', 6));
    ui->lineEdit_dwellTime->setText(QString::number(plan_->subranges.first().dwell_ms / 1000));

    //重新初始化所有简单图表
    auto reinitSimplePlot = [&](int xAxis, int yAxis, MyCustomplot *plot, const QString &ylabel, double yMin, double yMax, const QColor &color) {
        if (plot) {
            plot->destroyAll();
            plot->initValue(xAxis, yAxis, xMinMHz, xMaxMHz, yMin, yMax, tr("Frequency MHz"), ylabel);
            plot->setLine(xMinMHz, yMin, color); // 关键：重新创建graph(0)和items
            plot->replot();
        }
    };

    QColor color("#01C4E3");
    reinitSimplePlot(static_cast<int>(tpl->GeneratorOutputXAxisScale), static_cast<int>(tpl->GeneratorOutputYAxisScale), plotGeneratorOutput, tr("GeneratorOutput dBm"), tpl->GeneratorOutputYmin, tpl->GeneratorOutputYmax, color);
    reinitSimplePlot(static_cast<int>(tpl->AmplifierFPXAxisScale), static_cast<int>(tpl->AmplifierFPYAxisScale), plotAmplifierFWD, tr("AmplifierFWD dBm"), tpl->AmplifierFPYmin, tpl->AmplifierFPYmax, color);
    reinitSimplePlot(static_cast<int>(tpl->AmplifierRPXAxisScale), static_cast<int>(tpl->AmplifierRPYAxisScale), plotAmplifierREV, tr("AmplifierREV dBm"), tpl->AmplifierRPYmin, tpl->AmplifierRPYmax, color);
    reinitSimplePlot(static_cast<int>(tpl->VSWRXAxisScale), static_cast<int>(tpl->VSWRYAxisScale), plotVSWR, tr("VSWR"), tpl->VSWRYmin, tpl->VSWRYmax, QColor(72, 72, 255));

    // 重新初始化包含极限值线的复杂图表
    drawGlobalImmunityLimit(static_cast<int>(tpl->ImmunityLevelXAxisScale), static_cast<int>(tpl->ImmunityLevelYAxisScale)); // 这个函数已经包含了对 plotImmunityLevel 的 destroyAll 和 setLine
    if (!isRS && plan_->stage == TestProcessStage::Testing && plan_->levelMode == LevelModeType::Sensor)
        drawFwdPowerLimit();
    plotImmunityLevel->setYRange(tpl->ImmunityLevelYmin, tpl->ImmunityLevelYmax);
    // 4. 根据模板设置图表可见性
    plotGeneratorOutput->setVisible(tpl->CheckGeneratorOutput);
    plotAmplifierFWD->setVisible(tpl->CheckAmplifierFP);
    plotAmplifierREV->setVisible(tpl->CheckAmplifierRP);
    plotImmunityLevel->setVisible(tpl->CheckImmunityLevel);
    plotVSWR->setVisible(tpl->CheckVSWR);

    // 动态调整布局
    QList<MyCustomplot *> visiblePlots;
    if (tpl->CheckAmplifierFP)
        visiblePlots.append(plotAmplifierFWD);
    if (tpl->CheckAmplifierRP)
        visiblePlots.append(plotAmplifierREV);
    if (tpl->CheckGeneratorOutput)
        visiblePlots.append(plotGeneratorOutput);
    if (tpl->CheckImmunityLevel)
        visiblePlots.append(plotImmunityLevel);
    if (tpl->CheckVSWR)
        visiblePlots.append(plotVSWR);

    // 清除旧布局
    if (ui->testFrame->layout()) {
        delete ui->testFrame->layout();
    }

    // 创建新布局
    QGridLayout *gridLayout = new QGridLayout(ui->testFrame);
    gridLayout->setMargin(0);
    gridLayout->setSpacing(0);

    int count = visiblePlots.size();
    int numCols = (count > 3) ? 2 : 1;

    for (int i = 0; i < count; ++i) {
        int row = i / numCols;
        int col = i % numCols;
        visiblePlots[i]->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
        gridLayout->addWidget(visiblePlots[i], row, col);
    }

    return true;
}

/**
 * @brief TestWidget::buildLevelScanConfig
 * @details 从界面读取配置构建电平扫描参数。
 * @param config 输出的电平扫描配置。
 * @return 配置有效返回 true。
 */
bool TestWidget::buildLevelScanConfig(LevelScanningConfig &config)
{
    m_testResultsBuffer.clear();
    QList<OverviewItem> list = DataManager::instance()->getParameterTableOverviews();

    const int facTplId = DataManager::instance()->findIDByOverViewName(list, ParamTableKind::Transducer, ui->comboBox_factorTable->currentText());
    if (facTplId <= 0) {
        qWarning() << " factorTable id <= 0 ";
        return false;
    }
    const int attTplId = DataManager::instance()->findIDByOverViewName(list, ParamTableKind::Attenuation, ui->comboBox_attTable->currentText());
    if (attTplId <= 0) {
        qWarning() << " attTable id <= 0 ";
        return false;
    }

    config.factorTableID = facTplId;
    config.attTableID = attTplId;
    config.freqStart_Hz = ui->doubleSpinBox_startFreq->value() * 1e6;
    config.freqStop_Hz = ui->doubleSpinBox_stopFreq->value() * 1e6;

    config.stepMode = static_cast<AxisScale>(ui->comboBox_stepMode->currentIndex());

    if (config.stepMode == AxisScale::Linear)
        config.freqStep_Hz = ui->doubleSpinBox_stepFreq->value() * 1e6;
    else
        config.freqStep_Hz = ui->doubleSpinBox_stepFreq->value();

    bool isRS = (ui->radioButton_radiated->isChecked()) ? true : false;
    isRS ? config.type = HardwareTemplateType::RS : config.type = HardwareTemplateType::CS;
    config.powerUnit = ui->label_refLevelUnit->text();
    config.stage = m_stage;
    config.refLevel = ui->doubleSpinBox_refLevel->value();
    config.dwellTime = ui->doubleSpinBox_dwelltime->value();
    config.inputAtten = ui->doubleSpinBox_inputAtt->value();
    config.repeatCont = ui->doubleSpinBox_repeatCount->value();
    config.safetyMargin = ui->doubleSpinBox_safetyMargin->value();

    config.gAttributes.Model = ui->comboBox_Model->currentText();
    config.gAttributes.LibraryPath = DataManager::instance()->libraryPathForModel(ui->comboBox_Model->currentText());
    config.gAttributes.InterfaceType = static_cast<DeviceInterfaceType>(ui->comboBox_InterfaceType->currentIndex());
    config.gAttributes.type = DeviceType::Receiver;
    config.gAttributes.InterfaceAddr = ui->lineEdit_Addr->text();

    if (config.gAttributes.Model == STR_NO_DEVICE || config.gAttributes.Model.isEmpty()) {
        qWarning() << "device model Error";
        return false;
    }

    return true;
}

/**
 * @brief TestWidget::setCursor
 * @details 设置当前子区间和频点索引，并同步到执行线程。
 * @param subIdx 子区间索引。
 * @param freqIdx 频点索引。
 */
void TestWidget::setCursor(int subIdx, int freqIdx)
{
    currentSubrangeIndex_ = subIdx;
    currentFrequencyIndex_ = freqIdx;
    if (runner_) {
        QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                                  Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    }
}

/**
 * @brief TestWidget::refreshButtonState
 * @details 根据执行状态更新按钮可用性。
 */
void TestWidget::refreshButtonState()
{
    const bool idle = (lastState_ == "Idle");
    if (m_stage == TestProcessStage::LevelScanning) {
        ui->tBtn_stop->setEnabled(!idle);
        ui->tBtn_fwdPlay->setEnabled(idle);
        return;
    }

    ui->comboBox_template->setEnabled(idle);
    ui->tBtn_stop->setEnabled(!idle);
    ui->tBtn_singlePlay->setEnabled(idle && plan_);
    ui->tBtn_fwdPlay->setEnabled(idle && plan_);
    ui->tBtn_recPlay->setEnabled(idle && plan_);
    ui->tBtn_first->setEnabled(idle && plan_);
    ui->tBtn_previous->setEnabled(idle && plan_);
    ui->tBtn_next->setEnabled(idle && plan_);
    ui->tBtn_last->setEnabled(idle && plan_);
    ui->tBtn_save->setEnabled(idle && plan_);
    ui->tBtn_update->setEnabled(idle && plan_);
    ui->radioButton_H->setEnabled(idle && plan_);
    ui->radioButton_V->setEnabled(idle && plan_);

    bool isShow = false;
    if (plan_)
        isShow = (HardwareTemplateType::RS == static_cast<HardwareTemplateType>(plan_->flowKind)) ? true : false;
    ui->radioButton_H->setVisible(isShow);
    ui->radioButton_V->setVisible(isShow);

    if (m_stage == TestProcessStage::Calibration)
        ui->tBtn_save->setEnabled(true);
}

/**
 * @brief TestWidget::getGlobalFreqRangeHz
 * @details 获取当前计划覆盖的全局频率范围。
 * @param outMinHz 输出最小频率（Hz）。
 * @param outMaxHz 输出最大频率（Hz）。
 * @return 成功返回 true。
 */
bool TestWidget::getGlobalFreqRangeHz(double &outMinHz, double &outMaxHz) const
{
    if (!plan_)
        return false;

    double gmin = std::numeric_limits<double>::infinity();
    double gmax = -std::numeric_limits<double>::infinity();

    for (const auto &sr : plan_->subranges) {
        if (sr.freqPoints_Hz.isEmpty())
            continue;

        auto mm = std::minmax_element(sr.freqPoints_Hz.begin(), sr.freqPoints_Hz.end());
        gmin = std::min(gmin, *mm.first);
        gmax = std::max(gmax, *mm.second);
    }

    if (!std::isfinite(gmin) || !std::isfinite(gmax))
        return false;

    outMinHz = gmin;
    outMaxHz = gmax;
    return true;
}

/**
 * @brief TestWidget::buildGlobalLimitPolyline
 * @details 构建全局极限曲线点集。
 * @param xsMHz 输出频率点（MHz）。
 * @param ys 输出幅值点。
 * @return 数据有效返回 true。
 */
bool TestWidget::buildGlobalLimitPolyline(QVector<double> &xsMHz, QVector<double> &ys)
{
    xsMHz.clear();
    ys.clear();
    if (!plan_)
        return false;

    for (const auto &sr : plan_->subranges) {
        if (!sr.limitCurve)
            continue;
        const auto &grid = sr.freqPoints_Hz;
        const auto &ly = sr.limitCurve->ys;
        if (grid.size() != ly.size())
            continue;

        int start = 0;
        if (!xsMHz.isEmpty() && !grid.isEmpty()) {
            double prev = xsMHz.back() * 1e6;
            if (qFuzzyCompare(prev + 1.0, grid.front() + 1.0)) // 去掉重复边界点
                start = 1;
        }
        for (int i = start; i < grid.size(); ++i) {
            xsMHz.push_back(grid[i] / 1e6);
            ys.push_back(ly[i]);
        }
    }
    if (xsMHz.isEmpty() || ys.isEmpty() || xsMHz.size() != ys.size())
        return false;
    return true;
}

/**
 * @brief TestWidget::drawGlobalImmunityLimit
 * @details 绘制全局抗干扰极限曲线。
 */
void TestWidget::drawGlobalImmunityLimit(int xType, int yType)
{
    if (!plan_ || !plotImmunityLevel)
        return;

    // 全局 X 轴范围（MHz）
    double minHz = 0, maxHz = 0;
    if (!getGlobalFreqRangeHz(minHz, maxHz))
        return;
    const double xMinMHz = minHz / 1e6;
    const double xMaxMHz = maxHz / 1e6;

    // 3) 清空并初始化坐标轴
    plotImmunityLevel->destroyAll();
    //    const QString yLabel = QString("Immunity Level %1").arg(plan_->immunityUnit);
    const QString yLabel = QString("%1 %2").arg(tr("Immunity Level")).arg(plan_->immunityUnit);
    plotImmunityLevel->initValue(xType, yType, xMinMHz, xMaxMHz, 0, 1,
                                 tr("Frequency MHz"), yLabel);

    // 预留 graph(0) 给“测量曲线”，并创建标签/箭头
    const QColor measColor(0, 200, 0); // 绿色
    plotImmunityLevel->setLine(xMinMHz, 1, measColor);
    if (auto *g0 = plotImmunityLevel->graph(0)) {
        QPen pen = g0->pen();
        pen.setWidthF(1.5);
        g0->setPen(pen);
        g0->setScatterStyle(QCPScatterStyle::ssDisc);
    }

    double lastXMHz = std::numeric_limits<double>::quiet_NaN();
    double lastY = std::numeric_limits<double>::quiet_NaN();

    auto fuzzyEq = [](double a, double b) { return qFuzzyCompare(1.0 + a, 1.0 + b); };

    for (const auto &sr : plan_->subranges) {
        if (!sr.limitCurve)
            continue;
        const auto &grid = sr.freqPoints_Hz;
        const auto &ly = sr.limitCurve->ys;
        if (grid.isEmpty() || grid.size() != ly.size())
            continue;

        QVector<double> xData, yData, xData2, yData2;

        xData.reserve(grid.size() + 1);
        yData.reserve(ly.size() + 1);
        xData2.reserve(grid.size() + 1);
        yData2.reserve(ly.size() + 1);

        const double firstMHz = grid.front() / 1e6;
        const double firstY = ly.front();
        if (std::isfinite(lastXMHz) && fuzzyEq(firstMHz, lastXMHz) && fuzzyEq(firstY, lastY)) {
            xData << lastXMHz;
            yData << lastY;
            xData2 << lastXMHz;
            yData2 << lastY - plan_->cs_offset;
        }

        for (int i = 0; i < grid.size(); ++i) {
            xData << grid[i] / 1e6;
            yData << ly[i];
            xData2 << grid[i] / 1e6;
            yData2 << ly[i] - plan_->cs_offset;
        }

        plotImmunityLevel->setLine(xData, yData, QColor("#FD950E"), Qt::PenStyle::SolidLine);
        if (plan_->flowKind == HardwareTemplateType::CS && plan_->levelMode == LevelModeType::Sensor
            && plan_->cs_offsetCheck)
            plotImmunityLevel->setLine(xData2, yData2, Qt::red, Qt::PenStyle::DashLine);

        lastXMHz = xData.back();
        lastY = yData.back();
    }

    plotImmunityLevel->replot();
}

/**
 * @brief TestWidget::drawFwdPowerLimit
 * @details 绘制前向功率极限曲线。
 */
void TestWidget::drawFwdPowerLimit()
{
    if (!plan_ || !plotImmunityLevel)
        return;
    double lastXMHz = std::numeric_limits<double>::quiet_NaN();
    double lastY = std::numeric_limits<double>::quiet_NaN();

    auto fuzzyEq = [](double a, double b) { return qFuzzyCompare(1.0 + a, 1.0 + b); };

    for (const auto &sr : plan_->subranges) {
        if (!sr.cal)
            continue;

        const auto &grid = sr.freqPoints_Hz;
        QVector<double> ly;
        foreach (auto freq, sr.freqPoints_Hz) {
            Exec::CalPoint calPoint;
            if (!Utils::findCalibrationDataByFrequency(sr.cal, freq, calPoint)) {
                appendLog(LogLevel::Error, "not found cal freq");
                qCritical() << "not found cal data, freq: " + QString::number(freq);
                return;
            }
            ly.push_back(calPoint.fwd_dBm);
        }
        if (grid.isEmpty() || grid.size() != ly.size()) {
            qWarning() << "sr.freqPoints_Hz.size != ly.size, sr.freqPoints_Hz.size:" << grid.size() << ", ly.size:" << ly.size();
            continue;
        }

        QVector<double> xData, yData;

        xData.reserve(grid.size() + 1);
        yData.reserve(ly.size() + 1);

        const double firstMHz = grid.front() / 1e6;
        const double firstY = ly.front();
        if (std::isfinite(lastXMHz) && fuzzyEq(firstMHz, lastXMHz) && fuzzyEq(firstY, lastY)) {
            xData << lastXMHz;
            yData << lastY;
        }

        for (int i = 0; i < grid.size(); ++i) {
            xData << grid[i] / 1e6;
            yData << ly[i];
        }

        plotAmplifierFWD->setLine(xData, yData, QColor("#FD950E"), Qt::PenStyle::SolidLine);
        lastXMHz = xData.back();
        lastY = yData.back();
    }
    plotAmplifierFWD->replot();
}

// 初始化界面
/**
 * @brief TestWidget::initUiAndSignals
 * @details 初始化界面控件并连接必要的信号槽。
 */
void TestWidget::initUiAndSignals()
{
    // 模板变化
    connect(ui->comboBox_template, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &TestWidget::on_comboBox_template_currentIndexChanged);
    connect(DataManager::instance(), &DataManager::sig_testTemplatesChanged,
            this, &TestWidget::slot_testTemplatesChanged);

    ui->lineEdit_dwellTime->setReadOnly(true);
    ui->lineEdit_levelpeak->setReadOnly(true);
    ui->lineEdit_levelnow->setReadOnly(true);
    ui->lineEdit_levelgen->setReadOnly(true);
    ui->lineEdit_Freq->setReadOnly(true);
    ui->lineEdit_stopF->setReadOnly(true);
    ui->lineEdit_startF->setReadOnly(true);

    if (m_stage != TestProcessStage::Testing) {
        ui->tBtn_first->hide();
        ui->tBtn_previous->hide();
        ui->tBtn_recPlay->hide();

        ui->tBtn_singlePlay->hide();
        ui->tBtn_next->hide();
        ui->tBtn_last->hide();
    }
    ui->stackedWidget->setCurrentIndex(0);

    ui->tBtn_close->setToolTip(tr("Close"));
    ui->tBtn_close->hide();

    ui->lineEdit_Freq->setText(QString::number(m_startFreq, 'f', 6));
    ui->lineEdit_startF->setText(QString::number(m_startFreq, 'f', 6));
    ui->lineEdit_stopF->setText(QString::number(m_stopFreq, 'f', 6));
    ui->comboBox_template->addItem(STR_NO_TABLE, 0);
}

/**
 * @brief TestWidget::saveResult
 * @details 保存测试或校准结果至文件或数据库。
 */
void TestWidget::saveResult()
{
    //保存测试报告
    if (m_testResultsBuffer.size() <= 0) {
        QMessageBox::warning(this, tr("Warning"), tr("No data"));
        return;
    }
    bool ok = false;
    QString name;
    if (m_stage == TestProcessStage::Calibration) { //校准
        DataManager *dm = DataManager::instance();
        QList<OverviewItem> list = dm->getParameterTableOverviews();
        do {
            name = QInputDialog::getText(this, tr("Cal data save"), tr("Please enter the saved file name:"), QLineEdit::Normal,
                                         nullptr, &ok, Qt::WindowCloseButtonHint | Qt::MSWindowsFixedSizeDialogHint, Qt::ImhNone);

            if (!ok || name.isEmpty())
                return;

            bool isDuplicate = dm->overVivewNameExists(list, name);

            if (isDuplicate)
                QMessageBox::warning(this, tr("Name exists"), tr("The name '%1' already exists. Please enter a different name.").arg(name));
            else
                break;

        } while (true);
        OverviewItem newItem;
        newItem.id = -1;
        newItem.name = name;
        newItem.type = ParamTableKind::Cal;
        if (!DataManager::instance()->addParameterTable(newItem)) {
            qWarning() << "add ParameterTable failed！ itemName:" << newItem.name;
            appendLog(LogLevel::Error, "Save Cal Failed");
            return;
        }
        QList<CalTableConfig> tabList;
        foreach (auto tab, m_testResultsBuffer) {
            tabList.push_back(tab);
        }
        dm->saveCalibrationTable(newItem.id, name, tabList);
        appendLog(LogLevel::Info, "Save successfull");
    } else if (m_stage == TestProcessStage::Testing) { //测试
#ifdef TEST_DEBUG
        return;
#endif
        QString name = QInputDialog::getText(this, tr("Test data save"), tr("Please enter the saved file name:"),
                                             QLineEdit::Normal, nullptr, &ok,
                                             Qt::WindowCloseButtonHint | Qt::MSWindowsFixedSizeDialogHint,
                                             Qt::ImhNone);
        if (name.isEmpty())
            return;
        QString folderPath = QCoreApplication::applicationDirPath() + "/reports/";
        QDir dir(folderPath);
        if (!dir.exists())
            dir.mkpath(folderPath);
        QString screenPath = folderPath + "screenshot/";
        QDir dirs(screenPath);
        if (!dirs.exists())
            dirs.mkpath(screenPath);

        ReportInfo info;
        info.heading = tr("Test Report");
        info.paragraphList << tr("Test Frequency Range") + QString("（Hz）：%1-%2\n").arg(m_startFreq * 1e6).arg(m_stopFreq * 1e6)
                                  + tr("Test Time") + QString("：%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
        if (plotGeneratorOutput->isVisible()) {
            plotGeneratorOutput->getImage("plotGeneratorOutput");
            info.picList << folderPath + "screenshot/plotGeneratorOutput.png";
        }
        if (plotAmplifierFWD->isVisible()) {
            plotAmplifierFWD->getImage("plotAmplifierFWD");
            info.picList << folderPath + "screenshot/plotAmplifierFWD.png";
        }
        if (plotAmplifierREV->isVisible()) {
            plotAmplifierREV->getImage("plotAmplifierREV");
            info.picList << folderPath + "screenshot/plotAmplifierREV.png";
        }
        if (plotVSWR->isVisible()) {
            plotVSWR->getImage("plotVSWR");
            info.picList << folderPath + "screenshot/plotVSWR.png";
        }
        if (plotImmunityLevel->isVisible()) {
            plotImmunityLevel->getImage("plotImmunityLevel");
            info.picList << folderPath + "screenshot/plotImmunityLevel.png";
        }
        info.picHeight = 0;
        info.picWidth = 0;
        info.filename = name;
        info.savepath = folderPath;

        // 线程中操作文件
        Reportoutput outP;
        QFuture<bool> future = QtConcurrent::run(outP, &Reportoutput::writeRTFFile, info);
        QFutureWatcher<bool> *fw = new QFutureWatcher<bool>;
        connect(fw, &QFutureWatcher<void>::finished, [=] {
            if (fw->result())
                QMessageBox::information(this, tr("Information"), tr("Data save success!"));
            else
                QMessageBox::critical(this, tr("Error"), tr("Data save fail!"));
            delete fw;
        });
        fw->setFuture(future);
    }
}

/**
 * @brief TestWidget::saveLevelScanConfig
 * @details 将电平扫描配置持久化保存。
 */
void TestWidget::saveLevelScanConfig()
{
    QList<OverviewItem> list = DataManager::instance()->getParameterTableOverviews();

    const int facTplId = DataManager::instance()->findIDByOverViewName(list, ParamTableKind::Transducer, ui->comboBox_factorTable->currentText());
    const int attTplId = DataManager::instance()->findIDByOverViewName(list, ParamTableKind::Attenuation, ui->comboBox_attTable->currentText());
    LevelScanningConfig config;
    config.factorTableID = facTplId;
    config.attTableID = attTplId;
    config.freqStart_Hz = ui->doubleSpinBox_startFreq->value() * 1e6;
    config.freqStop_Hz = ui->doubleSpinBox_stopFreq->value() * 1e6;
    config.freqStep_Hz = ui->doubleSpinBox_stepFreq->value() * 1e6;
    bool isRS = (ui->radioButton_radiated->isChecked()) ? true : false;
    isRS ? config.type = HardwareTemplateType::RS : config.type = HardwareTemplateType::CS;
    config.powerUnit = ui->label_refLevelUnit->text();
    config.stage = m_stage;
    config.refLevel = ui->doubleSpinBox_refLevel->value();
    config.dwellTime = ui->doubleSpinBox_dwelltime->value();
    config.inputAtten = ui->doubleSpinBox_inputAtt->value();
    config.repeatCont = ui->doubleSpinBox_repeatCount->value();
    config.safetyMargin = ui->doubleSpinBox_safetyMargin->value();

    config.gAttributes.Model = ui->comboBox_Model->currentText();
    config.gAttributes.LibraryPath = DataManager::instance()->libraryPathForModel(ui->comboBox_Model->currentText());
    config.gAttributes.InterfaceType = static_cast<DeviceInterfaceType>(ui->comboBox_InterfaceType->currentIndex());
    config.gAttributes.type = DeviceType::Receiver;
    config.gAttributes.InterfaceAddr = ui->lineEdit_Addr->text();
    Utils::saveLevelScanConfig(config);
    QMessageBox::information(this, tr("成功"), tr("当前配置已保存。"));
}

/**
 * @brief TestWidget::loadLevelScanConfig
 * @details 读取并应用电平扫描配置。
 */
void TestWidget::loadLevelScanConfig()
{
    LevelScanningConfig config = Utils::loadLevelScanConfig();

    ui->doubleSpinBox_startFreq->setValue(config.freqStart_Hz / 1e6); // Hz -> MHz
    ui->doubleSpinBox_stopFreq->setValue(config.freqStop_Hz / 1e6);
    ui->doubleSpinBox_stepFreq->setValue(config.freqStep_Hz / 1e6);

    if (config.type == HardwareTemplateType::RS) {
        ui->radioButton_radiated->setChecked(true);
    } else {
        ui->radioButton_conducted->setChecked(true);
    }
    ui->label_refLevelUnit->setText(config.powerUnit);
    ui->doubleSpinBox_refLevel->setValue(config.refLevel);
    ui->doubleSpinBox_dwelltime->setValue(config.dwellTime);
    ui->doubleSpinBox_inputAtt->setValue(config.inputAtten);
    ui->doubleSpinBox_repeatCount->setValue(config.repeatCont);
    ui->doubleSpinBox_safetyMargin->setValue(config.safetyMargin);

    int modelIndex = ui->comboBox_Model->findText(config.gAttributes.Model);
    if (modelIndex >= 0) {
        ui->comboBox_Model->setCurrentIndex(modelIndex);
    } else {
        ui->comboBox_Model->setCurrentIndex(-1); // 清除选择
        qWarning() << "Model" << config.gAttributes.Model << "not found in combo box.";
    }

    int interfaceTypeIndex = static_cast<int>(config.gAttributes.InterfaceType);
    if (interfaceTypeIndex >= 0 && interfaceTypeIndex < ui->comboBox_InterfaceType->count()) {
        ui->comboBox_InterfaceType->setCurrentIndex(interfaceTypeIndex);
    } else {
        ui->comboBox_InterfaceType->setCurrentIndex(-1);
        qWarning() << "InterfaceType index" << interfaceTypeIndex << "is invalid for combo box.";
    }
    QString add = config.gAttributes.InterfaceAddr;
    if (add.isEmpty())
        add = "TCPIP0::192.168.1.40::5025::SOCKET";
    ui->lineEdit_Addr->setText(add);

    QList<OverviewItem> list = DataManager::instance()->getParameterTableOverviews();
    QString factorTableName = DataManager::instance()->findNameByOverViewID(list, ParamTableKind::Transducer, config.factorTableID);
    if (!factorTableName.isEmpty()) {
        int index = ui->comboBox_factorTable->findText(factorTableName);
        if (index >= 0) {
            ui->comboBox_factorTable->setCurrentIndex(index);
        } else {
            ui->comboBox_factorTable->setCurrentIndex(-1);
            qWarning() << "Factor table with ID" << config.factorTableID << "and name" << factorTableName << "not found in combo box.";
        }
    }
    QString AttTableName = DataManager::instance()->findNameByOverViewID(list, ParamTableKind::Attenuation, config.attTableID);
    if (!AttTableName.isEmpty()) {
        int index = ui->comboBox_attTable->findText(AttTableName);
        if (index >= 0) {
            ui->comboBox_attTable->setCurrentIndex(index);
        } else {
            ui->comboBox_attTable->setCurrentIndex(-1);
            qWarning() << "Att table with ID" << config.attTableID << "and name" << AttTableName << "not found in combo box.";
        }
    }
}

/**
 * @brief TestWidget::on_tBtn_singlePlay_clicked
 * @details 执行单点测试流程。
 */
void TestWidget::on_tBtn_singlePlay_clicked()
{
    if (!plan_ && !buildPlanFromCurrentTemplate())
        return;
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    QMetaObject::invokeMethod(runner_, "runSingle", Qt::QueuedConnection);
    isSingleTest = true;
    refreshButtonState();
}

/**
 * @brief TestWidget::on_tBtn_fwdPlay_clicked
 * @details 启动正向扫描或电平扫描流程。
 */
void TestWidget::on_tBtn_fwdPlay_clicked()
{
    if (m_stage == TestProcessStage::LevelScanning) {
        LevelScanningConfig config;
        if (!buildLevelScanConfig(config)) {
            qCritical() << "buildLevelScanConfig error";
            appendLog(LogLevel::Error, "buildLevelScanConfig error");
            return;
        }
        if (!runner_) {
            qCritical() << "TestWidget: runner_ is nullptr when trying to emit signal!";
            return;
        }
        plotImmunityLevel->destroyAll();
        m_levelScanData_x.clear();
        m_levelScanData_y.clear();
        const int xType = ui->comboBox_stepMode->currentIndex();
        QString unit = (ui->radioButton_radiated->isChecked()) ? STR_UNIT_VM : "dB\u03BCA";

        const QString yLabel = QString("%1 %2").arg(tr("Immunity Level")).arg(unit);
        plotImmunityLevel->initValue(xType, 0, ui->doubleSpinBox_startFreq->value(), ui->doubleSpinBox_stopFreq->value(), 0, 1,
                                     tr("Frequency MHz"), yLabel);

        QMetaObject::invokeMethod(runner_, "runLevelScanning", Qt::QueuedConnection, Q_ARG(LevelScanningConfig, config));
        isSingleTest = false;
        refreshButtonState();
        //        plotImmunityLevel->setXRange(ui.dou)

        return;
    }

    if (!plan_ && !buildPlanFromCurrentTemplate())
        return;
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    QMetaObject::invokeMethod(runner_, "runSweepForward", Qt::QueuedConnection);
    isSingleTest = false;
    refreshButtonState();
}

/**
 * @brief TestWidget::on_tBtn_recPlay_clicked
 * @details 启动反向扫描流程。
 */
void TestWidget::on_tBtn_recPlay_clicked()
{
    if (!plan_ && !buildPlanFromCurrentTemplate())
        return;
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    QMetaObject::invokeMethod(runner_, "runSweepBackward", Qt::QueuedConnection);
    isSingleTest = false;
    refreshButtonState();
}

/**
 * @brief TestWidget::on_tBtn_first_clicked
 * @details 定位到首个频点并更新界面。
 */
void TestWidget::on_tBtn_first_clicked()
{
    if (!plan_)
        return;
    currentSubrangeIndex_ = 0;
    currentFrequencyIndex_ = 0;
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    plotImmunityLevel->setPoint("MHz", plan_->subranges[currentSubrangeIndex_].freqPoints_Hz[currentFrequencyIndex_], 0);
    refreshButtonState();
}

/**
 * @brief TestWidget::on_tBtn_previous_clicked
 * @details 跳转到前一个频点。
 */
void TestWidget::on_tBtn_previous_clicked()
{
    if (!plan_)
        return;
    if (currentFrequencyIndex_ > 0) {
        currentFrequencyIndex_--;
    } else if (currentSubrangeIndex_ > 0) {
        currentSubrangeIndex_--;
        currentFrequencyIndex_ = plan_->subranges[currentSubrangeIndex_].freqPoints_Hz.size() - 1;
    }
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    plotImmunityLevel->setPoint("MHz", plan_->subranges[currentSubrangeIndex_].freqPoints_Hz[currentFrequencyIndex_], 0);
    refreshButtonState();
}

/**
 * @brief TestWidget::on_tBtn_stop_clicked
 * @details 停止当前测试执行。
 */
void TestWidget::on_tBtn_stop_clicked()
{
    if (runner_)
        runner_->stop();
}

/**
 * @brief TestWidget::on_tBtn_next_clicked
 * @details 跳转到下一个频点并更新游标。
 */
void TestWidget::on_tBtn_next_clicked()
{
    if (!plan_)
        return;
    const auto &sr = plan_->subranges[currentSubrangeIndex_];
    if (currentFrequencyIndex_ + 1 < sr.freqPoints_Hz.size()) {
        currentFrequencyIndex_++;
    } else if (currentSubrangeIndex_ + 1 < plan_->subranges.size()) {
        currentSubrangeIndex_++;
        currentFrequencyIndex_ = 0;
    }
    plotImmunityLevel->setPoint("MHz", plan_->subranges[currentSubrangeIndex_].freqPoints_Hz[currentFrequencyIndex_], 0);
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    refreshButtonState();
}

/**
 * @brief TestWidget::on_tBtn_last_clicked
 * @details 定位到最后一个频点。
 */
void TestWidget::on_tBtn_last_clicked()
{
    if (!plan_)
        return;
    currentSubrangeIndex_ = plan_->subranges.size() - 1;
    currentFrequencyIndex_ = plan_->subranges[currentSubrangeIndex_].freqPoints_Hz.size() - 1;
    QMetaObject::invokeMethod(runner_, "setCursor", Qt::QueuedConnection,
                              Q_ARG(int, currentSubrangeIndex_), Q_ARG(int, currentFrequencyIndex_));
    plotImmunityLevel->setPoint("MHz", plan_->subranges[currentSubrangeIndex_].freqPoints_Hz[currentFrequencyIndex_], 0);
    refreshButtonState();
}

/**
 * @brief TestWidget::on_tBtn_save_clicked
 * @details 保存当前阶段的结果或配置。
 */
void TestWidget::on_tBtn_save_clicked()
{
    if (m_stage == TestProcessStage::LevelScanning) {
        saveLevelScanConfig();
        return;
    }
    saveResult();
}

/**
 * @brief TestWidget::on_tBtn_update_clicked
 * @details 刷新图表或清空电平扫描数据。
 */
void TestWidget::on_tBtn_update_clicked()
{
    if (m_stage == TestProcessStage::LevelScanning) {
        plotImmunityLevel->destroyAll();
        m_levelScanData_x.clear();
        m_levelScanData_y.clear();
        return;
    }
    QObjectList children = ui->testFrame->children();
    for (QObject *child : children) {
        delete child;
    }
    initPlot();
    slot_testTemplatesChanged();
}

/**
 * @brief TestWidget::on_radioButton_V_clicked
 * @details 切换到垂直极化并重建计划。
 * @param checked 单选按钮状态。
 */
void TestWidget::on_radioButton_V_clicked(bool checked)
{
    Q_UNUSED(checked)
    buildPlanFromCurrentTemplate();
    refreshButtonState();
}

/**
 * @brief TestWidget::on_radioButton_H_clicked
 * @details 切换到水平极化并重建计划。
 * @param checked 单选按钮状态。
 */
void TestWidget::on_radioButton_H_clicked(bool checked)
{
    Q_UNUSED(checked)
    buildPlanFromCurrentTemplate();
    refreshButtonState();
}

/**
 * @brief TestWidget::slot_testTemplatesChanged
 * @details 处理测试模板列表变更并维护选择。
 */
void TestWidget::slot_testTemplatesChanged()
{
    const int keepId = ui->comboBox_template->currentData().toInt();
    QSignalBlocker blk(ui->comboBox_template);
    ui->comboBox_template->clear();
    ui->comboBox_template->addItem(STR_NO_TABLE, 0);
    for (const auto &ov : DataManager::instance()->getTestTemplateOverviews()) {
        ui->comboBox_template->addItem(ov.name, ov.id);
    }
    int indexToSelect = Utils::findComboBoxIndexByData(ui->comboBox_template, keepId);
    if (indexToSelect < 0 && ui->comboBox_template->count() > 0) {
        // 如果之前选中的模板被删除了，就默认选中第一个
        indexToSelect = 0;
    }
    // 如果列表为空，则 indexToSelect 保持为 -1
    if (ui->comboBox_template->count() == 0) {
        indexToSelect = -1;
    }

    // --- 设置当前项，并手动触发刷新逻辑 ---
    ui->comboBox_template->setCurrentIndex(indexToSelect);
    if (lastState_ == "Idle") {
        // 检查是否可以进行刷新
        if (indexToSelect > 0) {
            on_comboBox_template_currentIndexChanged(indexToSelect);
        } else {
            // 如果模板列表变为空，需要手动清空界面
            plan_.reset(); // 清空测试计划

            // 清空图表（可以调用一个辅助函数）
            auto clearPlot = [](MyCustomplot *plot) {
                if (plot) {
                    plot->destroyAll(); // 清空所有图层
                    // 可以选择调用 initValue 来显示一个空的默认坐标轴
                    plot->initValue(0, 0, 1, 1000, 0, 10, "Frequency MHz", "Value");
                    plot->replot();
                }
            };
            clearPlot(plotGeneratorOutput);
            clearPlot(plotImmunityLevel);
            clearPlot(plotAmplifierFWD);
            clearPlot(plotAmplifierREV);
            clearPlot(plotVSWR);

            // 清空UI文本显示
            ui->lineEdit_startF->clear();
            ui->lineEdit_stopF->clear();
            ui->lineEdit_Freq->clear();
            // ... 清空其他文本框 ...

            refreshButtonState(); // 更新按钮状态
        }
    }
}

/**
 * @brief TestWidget::appendLog
 * @details 将日志消息附加到界面文本框。
 * @param lv 日志级别。
 * @param msg 日志内容。
 */
void TestWidget::appendLog(LogLevel lv, const QString &msg)
{
    if (!ui->textEdit)
        return;
    if (msg.contains("read Power")) {
        liveReadPower(msg);
        return;
    }

    QString preStr;
    // 颜色
    QColor color = Qt::white;
    switch (lv) {
    case LogLevel::Info:
        color = Qt::white;
        preStr = "Inf:";
        break;
    case LogLevel::Warning:
        color = QColor("#F7C948");
        preStr = "War:";
        break; // 黄
    case LogLevel::Error:
        color = QColor("#FF4D4F");
        preStr = "Err:";
        break; // 红
    case LogLevel::Debug:
        preStr = "Deb:";
        color = QColor("#9AA0A6");
        break; // 灰
    }

    // 时间前缀
    const QString line = QString("%1%2]").arg(preStr).arg(msg);

    // 插入有颜色的一行
    QTextCursor c = ui->textEdit->textCursor();
    c.movePosition(QTextCursor::End);
    QTextCharFormat fmt;
    fmt.setForeground(color);
    // 可选：等宽字体
    QFont f("Consolas");
    f.setStyleHint(QFont::Monospace);
    fmt.setFont(f);
    c.insertText(line + "\n", fmt);
    ui->textEdit->setTextCursor(c);

    // 自动滚到底
    auto *sb = ui->textEdit->verticalScrollBar();
    sb->setValue(sb->maximum());

    // 简单限长（删最前一行，防爆内存）
    static const int kMaxBlocks = 2000;
    if (ui->textEdit->document()->blockCount() > kMaxBlocks) {
        QTextCursor t(ui->textEdit->document());
        t.movePosition(QTextCursor::Start);
        t.select(QTextCursor::BlockUnderCursor);
        t.removeSelectedText();
        t.deleteChar(); // 删除换行
    }
}

/**
 * @brief TestWidget::liveReadPower
 * @details 解析实时功率读取并更新显示。
 * @param msg 原始消息文本。
 */
void TestWidget::liveReadPower(const QString &msg)
{
    QString str = msg;
    str = str.remove("(dBm)");
    str = str.remove("(dBuV)");
    str = str.remove("(Vm/dBm)");
    QStringList parts = str.split(", ");
    if (parts.size() < 4)
        return;
    auto extractValue = [](const QString &part) -> QString {
        return part.split(' ').last();
    };

    auto formatNumber = [](const QString &num) -> QString {
        int dotIndex = num.indexOf('.');
        return (dotIndex != -1 && dotIndex + 4 <= num.length()) ? num.left(dotIndex + 4) : num;
    };

    ui->label_LD_fwd->setText(formatNumber(extractValue(parts[1])));
    ui->label_LD_rev->setText(formatNumber(extractValue(parts[2])));
    ui->label_LD_Mon->setText(formatNumber(extractValue(parts[3])));
}
/**
 * @brief TestWidget::on_comboBox_template_currentIndexChanged
 * @details 模板选择变化时更新测试计划。
 * @param index 新索引。
 */
void TestWidget::on_comboBox_template_currentIndexChanged(int index)
{
    Q_UNUSED(index)
    buildPlanFromCurrentTemplate();
    refreshButtonState();
}

/**
 * @brief TestWidget::onStateChanged
 * @details 接收测试状态变化并刷新界面。
 * @param state 状态字符串。
 */
void TestWidget::onStateChanged(const QString &state)
{
    lastState_ = state;
    refreshButtonState();
}

/**
 * @brief TestWidget::onPointMeasured
 * @details 处理单点测量结果并刷新图表。
 * @param p 测量结果数据。
 */
void TestWidget::onPointMeasured(const MeasurementPoint &p)
{
    ui->lineEdit_Freq->setText(QString::number(p.calTable.Freq / 1e6));

    ui->lineEdit_levelpeak->setText(QString::number(p.calTable.LimitImmunityLevel));
    ui->lineEdit_levelnow->setText(QString::number(p.calTable.ImmunityLevel, 'f', 3));
    ui->lineEdit_levelgen->setText(QString::number(p.calTable.Amplitude, 'f', 3));
    if (isSingleTest) {
        plotGeneratorOutput->setPoint("dBm", p.calTable.Freq, p.calTable.Amplitude);
        plotAmplifierFWD->setPoint("dBm", p.calTable.Freq, p.calTable.FwdPower);
        plotAmplifierREV->setPoint("dBm", p.calTable.Freq, p.calTable.RevPower);
        plotVSWR->setPoint("", p.calTable.Freq, p.calTable.swr);
        plotImmunityLevel->setPoint(m_currentImmunityLevelUnit, p.calTable.Freq, p.calTable.ImmunityLevel);
    } else {
        plotGeneratorOutput->addPoint("dBm", p.calTable.Freq, p.calTable.Amplitude);
        plotAmplifierFWD->addPoint("dBm", p.calTable.Freq, p.calTable.FwdPower);
        plotAmplifierREV->addPoint("dBm", p.calTable.Freq, p.calTable.RevPower);
        plotVSWR->addPoint("", p.calTable.Freq, p.calTable.swr);
        plotImmunityLevel->addPoint(m_currentImmunityLevelUnit, p.calTable.Freq, p.calTable.ImmunityLevel);
    }
    m_testResultsBuffer[p.calTable.Freq] = p.calTable;
    currentSubrangeIndex_ = p.subrangeIndex;
    currentFrequencyIndex_ = p.frequencyIndex;
#if 0
    if (!m_testResultsBuffer.isEmpty()) {
        double minAmp = 1000;
        double maxAmp = -1000;
        double minFwd = 1000;
        double maxFwd = -1000;
        double minRev = 1000;
        double maxRev = -1000;
        double minImm = 1000;
        double maxImm = -1000;
        for (auto it = m_testResultsBuffer.begin(); it != m_testResultsBuffer.end(); ++it) {
            const auto &val = it.value();

            if (val.Amplitude < minAmp)
                minAmp = val.Amplitude;
            if (val.Amplitude > maxAmp)
                maxAmp = val.Amplitude;

            if (val.FwdPower < minFwd)
                minFwd = val.FwdPower;
            if (val.FwdPower > maxFwd)
                maxFwd = val.FwdPower;

            if (val.RevPower < minRev)
                minRev = val.RevPower;
            if (val.RevPower > maxRev)
                maxRev = val.RevPower;

            //考虑实际值和限值
            if (val.ImmunityLevel < minImm)
                minImm = val.ImmunityLevel;
            if (val.ImmunityLevel > maxImm)
                maxImm = val.ImmunityLevel;

            if (val.LimitImmunityLevel < minImm)
                minImm = val.LimitImmunityLevel;
            if (val.LimitImmunityLevel > maxImm)
                maxImm = val.LimitImmunityLevel;
        }

        double padding = 2.0;
        if (plotGeneratorOutput)
            plotGeneratorOutput->setYRange(minAmp - padding, maxAmp + padding);
        if (plotAmplifierFWD)
            plotAmplifierFWD->setYRange(minFwd - padding, maxFwd + padding);
        if (plotAmplifierREV)
            plotAmplifierREV->setYRange(minRev - padding, maxRev + padding);
        if (plotImmunityLevel)
            plotImmunityLevel->setYRange(minImm - padding, maxImm + padding);
    }
#endif
}

double minValue, maxValue = ERROR_VALUE;
/**
 * @brief TestWidget::onLevelScanningData
 * @details 接收电平扫描数据并更新曲线。
 * @param data 扫描数据对象。
 */
void TestWidget::onLevelScanningData(const LevelScanningData &data)
{
#ifdef _ISALW
    // m_levelScanData_x = data.freqPoints_Hz;
    // m_levelScanData_y = data.levelPoints;
    m_levelScanData_x.append(data.freqPoints_Hz);
    m_levelScanData_y.append(data.levelPoints);
    // qDebug() << "data:" << data.freqPoints_Hz;
    // qDebug() << "data:" << data.levelPoints;
    // qDebug() << "data:" << data.scanCount;

#else
    plotImmunityLevel->destroyAll();
    m_levelScanData_x = data.freqPoints_Hz;
    m_levelScanData_y = data.levelPoints;
#endif

    auto [minIt, maxIt] = std::minmax_element(m_levelScanData_y.begin(), m_levelScanData_y.end());
    if (minValue > *minIt)
        minValue = *minIt;
    if (maxValue < *maxIt)
        maxValue = *maxIt;
    // qDebug() << "max:" << maxValue << ", min: " << minValue;
    //    plotImmunityLevel->setYRange(minValue - 10, maxValue + 10);
    //    plotImmunityLevel->setYRange(minValue - 1, maxValue + 1);
    plotImmunityLevel->setYRange(minValue, maxValue);
    plotImmunityLevel->setLine(m_levelScanData_x, m_levelScanData_y, QColor("#01C4E3"), Qt::PenStyle::SolidLine);
    if (m_levelScanData_x.last() >= ui->doubleSpinBox_stopFreq->value() && data.scanCount < ui->doubleSpinBox_repeatCount->value()) {
        qDebug() << "last__freq: " << m_levelScanData_x.last();
        plotImmunityLevel->destroyAll();
        m_levelScanData_x.clear();
        m_levelScanData_y.clear();
        QThread::msleep(1000);
    }
    plotImmunityLevel->replot();
    plotImmunityLevel->update();
}

/**
 * @brief TestWidget::onSweepFinished
 * @details 扫描完成后保存结果并记录日志。
 */
void TestWidget::onSweepFinished()
{
    saveResult();
    appendLog(LogLevel::Info, "Sweep finished.");
}

/**
 * @brief TestWidget::onManualChangeRequired
 * @details 当需要人工切换时弹窗提示用户。
 * @param title 对话框标题。
 * @param message 提示信息。
 */
void TestWidget::onManualChangeRequired(const QString &title, const QString &message)
{
    // 人工切换提示弹窗
    QMessageBox box(this);
    box.setIcon(QMessageBox::Information);
    box.setWindowTitle(title);
    box.setText(message + "\n\n请完成切换后选择操作：");
    QPushButton *btnResume = box.addButton(tr("继续"), QMessageBox::AcceptRole);
    //    QPushButton *btnStop = box.addButton(tr("停止"), QMessageBox::RejectRole);
    box.exec();

    if (box.clickedButton() == btnResume) {
        runner_->resume();
    } else {
        runner_->stop();
    }
}

/**
 * @brief TestWidget::on_radioButton_radiated_toggled
 * @details 切换辐射模式时更新参考单位。
 * @param checked 单选按钮状态。
 */
void TestWidget::on_radioButton_radiated_toggled(bool checked)
{
    if (checked)
        ui->label_refLevelUnit->setText(STR_UNIT_DBM);
    else
        ui->label_refLevelUnit->setText("dB\u03BCV");
}

/**
 * @brief findSegmentMaximaByCountPrecise
 * @details 按分段数量提取曲线的局部最大值。
 * @param xData 频率数据。
 * @param yData 幅值数据。
 * @param segmentCount 分段数量。
 * @return 局部最大值的频率和幅值对。
 */
QPair<QVector<double>, QVector<double>> findSegmentMaximaByCountPrecise(const QVector<double> &xData, const QVector<double> &yData, int segmentCount)
{
    QVector<double> maxFrequencies;
    QVector<double> maxAmplitudes;

    // 输入验证
    if (xData.isEmpty() || yData.isEmpty() || xData.size() != yData.size()) {
        return qMakePair(maxFrequencies, maxAmplitudes);
    }

    if (segmentCount <= 0) {
        return qMakePair(maxFrequencies, maxAmplitudes);
    }

    int totalPoints = xData.size();

    // 特殊情况处理
    if (segmentCount == 1) {
        // 只要一个分段，返回全局最大值
        double globalMax = yData.first();
        int maxIndex = 0;
        for (int i = 1; i < totalPoints; ++i) {
            if (yData[i] > globalMax) {
                globalMax = yData[i];
                maxIndex = i;
            }
        }
        maxFrequencies.append(xData[maxIndex]);
        maxAmplitudes.append(globalMax);
        return qMakePair(maxFrequencies, maxAmplitudes);
    }

    if (segmentCount >= totalPoints) {
        // 分段数大于等于点数，返回所有点
        return qMakePair(QVector<double>(xData), QVector<double>(yData));
    }

    // 初始化结果数组
    maxFrequencies.reserve(segmentCount);
    maxAmplitudes.reserve(segmentCount);

    // 计算分段边界（包括起点和终点）
    QVector<int> segmentBoundaries;
    segmentBoundaries.reserve(segmentCount + 1);

    for (int i = 0; i <= segmentCount; ++i) {
        double position = static_cast<double>(i) / static_cast<double>(segmentCount) * (totalPoints - 1);
        int index = static_cast<int>(std::round(position));
        index = std::max(0, std::min(index, totalPoints - 1));
        segmentBoundaries.append(index);
    }

    // 去除重复的边界点
    for (int i = 1; i < segmentBoundaries.size(); ++i) {
        if (segmentBoundaries[i] <= segmentBoundaries[i - 1]) {
            segmentBoundaries[i] = segmentBoundaries[i - 1] + 1;
        }
    }

    // 确保最后一个边界是正确的
    segmentBoundaries.last() = totalPoints - 1;

    // 处理每个分段
    for (int seg = 0; seg < segmentCount; ++seg) {
        int startIndex = segmentBoundaries[seg];
        int endIndex = segmentBoundaries[seg + 1];

        // 确保有效范围
        if (startIndex >= endIndex) {
            continue;
        }

        // 在当前段中寻找最大值
        double maxAmplitude = yData[startIndex];
        int maxIndex = startIndex;

        for (int i = startIndex + 1; i <= endIndex && i < totalPoints; ++i) {
            if (yData[i] > maxAmplitude) {
                maxAmplitude = yData[i];
                maxIndex = i;
            }
        }

        maxFrequencies.append(xData[maxIndex]);
        maxAmplitudes.append(maxAmplitude);
    }

    maxFrequencies.insert(0, xData.first());
    maxFrequencies.append(xData.last());
    maxAmplitudes.insert(0, yData.first());
    maxAmplitudes.append(yData.last());

    return qMakePair(maxFrequencies, maxAmplitudes);
}

/**
 * @brief TestWidget::on_btn_buildPoints_clicked
 * @details 根据扫描数据生成极值点并绘制。
 */
void TestWidget::on_btn_buildPoints_clicked()
{
    auto result = findSegmentMaximaByCountPrecise(m_levelScanData_x, m_levelScanData_y, ui->doubleSpinBox_subCount->value());

    m_limit_x = result.first;
    m_limit_y = result.second;
    plotImmunityLevel->setLine(m_limit_x, m_limit_y, Qt::green);
    plotImmunityLevel->replot();
}

/**
 * @brief TestWidget::on_btn_buildLimit_clicked
 * @details 将极值点保存为极限线数据。
 */
void TestWidget::on_btn_buildLimit_clicked()
{
    //保存测试报告
    if (m_limit_x.size() <= 0 || m_limit_y.size() <= 0 || m_limit_x.size() != m_limit_y.size()) {
        QMessageBox::warning(this, tr("Warning"), tr("No data or data invalid"));
        return;
    }
    bool ok = false;
    QString name;
    DataManager *dm = DataManager::instance();
    QList<OverviewItem> list = dm->getParameterTableOverviews();
    do {
        name = QInputDialog::getText(this, tr("Limit data save"), tr("Please enter the saved file name:"), QLineEdit::Normal,
                                     nullptr, &ok, Qt::WindowCloseButtonHint | Qt::MSWindowsFixedSizeDialogHint, Qt::ImhNone);

        if (!ok || name.isEmpty())
            return;

        bool isDuplicate = dm->overVivewNameExists(list, name);

        if (isDuplicate)
            QMessageBox::warning(this, tr("Name exists"), tr("The name '%1' already exists. Please enter a different name.").arg(name));
        else
            break;

    } while (true);
    OverviewItem newItem;
    newItem.id = -1;
    newItem.name = name;
    newItem.type = ParamTableKind::LimitLine;
    if (!DataManager::instance()->addParameterTable(newItem)) {
        qWarning() << "add ParameterTable failed！ itemName:" << newItem.name;
        appendLog(LogLevel::Error, "Save Failed");
        return;
    }
    LimitLineList tableList;
    tableList.yUnit = STR_UNIT_HZ;
    tableList.yUnit = (ui->radioButton_radiated->isChecked()) ? STR_UNIT_VM : "dB\u03BCA";
    for (int i = 0; i < m_limit_x.size(); ++i) {
        LimitLineConfig conf;
        conf.Freq = m_limit_x[i] * 1e6;
        if (ui->radioButton_radiated->isChecked())
            conf.Limit = CalcUtils::dB2Vm(m_limit_y[i], ui->doubleSpinBox_safetyMargin->value());
        else
            conf.Limit = m_limit_y[i] + ui->doubleSpinBox_safetyMargin->value();
        tableList.limitList.push_back(conf);
    }

    dm->saveLimitLineTable(newItem.id, name, tableList);
    appendLog(LogLevel::Info, "Save successfull");
}

/**
 * @brief TestWidget::slot_parameterTablesChanged
 * @details 参数表变更时刷新转换因子列表。
 */
void TestWidget::slot_parameterTablesChanged()
{
    ui->comboBox_factorTable->clear();
    foreach (auto item, DataManager::instance()->getParameterTableOverviews()) {
        if (item.type == ParamTableKind::Transducer) {
            ui->comboBox_factorTable->addItem(item.name);
        }
    }
    ui->comboBox_attTable->clear();
    foreach (auto item, DataManager::instance()->getParameterTableOverviews()) {
        if (item.type == ParamTableKind::Attenuation) {
            ui->comboBox_attTable->addItem(item.name);
        }
    }
}

void TestWidget::on_comboBox_stepMode_currentIndexChanged(int index)
{
    AxisScale as = static_cast<AxisScale>(index);
    // if (AxisScale::Linear == as)
    // ui->label_33->setText("MHz");
    // else
    // ui->label_33->setText("%");
    if (plotImmunityLevel)
        plotImmunityLevel->setScaleTypeX(static_cast<QCPAxis::ScaleType>(as));
}
